package pl.edu.icm.saos.api.services.exceptions;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import pl.edu.icm.saos.api.services.exceptions.status.ErrorReason;
import pl.edu.icm.saos.api.services.representations.ErrorRepresentation;
import pl.edu.icm.saos.common.json.JsonFormatter;
/**
* Exception handler for restful api controllers.
* Every controller in restful api should extend this handler.
* @author pavtel
*/
public class ControllersEntityExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(ControllersEntityExceptionHandler.class);
private JsonFormatter jsonFormatter;
//------------------------ LOGIC --------------------------
@ExceptionHandler(ElementDoesNotExistException.class)
public ResponseEntity<Map<String, Object>> handelIllegalArgumentError(Exception ex){
ErrorReason errorStatus = ErrorReason.ELEMENT_DOES_NOT_EXIST_ERROR;
ErrorRepresentation.Builder errorRepresentation = create(errorStatus , ex);
return createErrorResponse(errorRepresentation, errorStatus);
}
@ExceptionHandler(WrongRequestParameterException.class)
public ResponseEntity<Map<String, Object>> handleWrongRequestParameterError(WrongRequestParameterException ex){
ErrorReason errorStatus = ErrorReason.WRONG_REQUEST_PARAMETER_ERROR;
ErrorRepresentation.Builder builder = create(errorStatus, ex);
builder.propertyName(ex.getParameterName());
return createErrorResponse(builder, errorStatus);
}
@ExceptionHandler(BindException.class)
public ResponseEntity<Map<String, Object>> handleConversionFailedError(BindException ex) {
ErrorReason errorStatus = ErrorReason.WRONG_REQUEST_PARAMETER_ERROR;
ErrorRepresentation.Builder builder = create(errorStatus, ex);
if(causedByFieldError(ex)){
FieldError fieldError = extractFieldError(ex);
builder.propertyName(fieldError.getField());
String message = String.format("parameter '%s' : can't have value '%s'", fieldError.getField(), fieldError.getRejectedValue());
builder.message(message);
}
return createErrorResponse(builder, errorStatus);
}
@ExceptionHandler(TypeMismatchException.class)
public ResponseEntity<Map<String, Object>> handleTypeMismatchError(TypeMismatchException ex){
ErrorReason errorStatus = ErrorReason.WRONG_REQUEST_PARAMETER_ERROR;
ErrorRepresentation.Builder builder = create(errorStatus, ex);
builder.propertyName(ex.getPropertyName());
String message = String.format("incorrect parameter value '%s'", ex.getValue());
builder.message(message);
return createErrorResponse(builder, errorStatus);
}
@ExceptionHandler(PageDoesNotExistException.class)
public ResponseEntity<Map<String, Object>> handleNotExisingPageError(Exception ex){
ErrorReason errorStatus = ErrorReason.PAGE_DOES_NOT_EXIST_ERROR;
ErrorRepresentation.Builder errorRepresentation = create(errorStatus , ex);
return createErrorResponse(errorRepresentation, errorStatus);
}
@ExceptionHandler(MethodNotSupportedException.class)
public ResponseEntity<Map<String, Object>> handleNotSupportedMethodError(MethodNotSupportedException ex) {
ErrorReason errorStatus = ErrorReason.UNSUPPORTED_HTTP_METHOD_ERROR;
ErrorRepresentation.Builder builder = create(errorStatus, ex);
String message = String.format("Not supported method '%s', only %s allowed", ex.getRequestedMethod(), ex.getSupportedMethod());
builder.message(message);
return createErrorResponse(builder, errorStatus);
}
@ExceptionHandler(MediaTypeNotSupportedException.class)
public void handleInvalidAcceptHeaderError(HttpServletRequest request, HttpServletResponse response, MediaTypeNotSupportedException ex) throws IOException {
ErrorReason errorStatus = ErrorReason.UNSUPPORTED_MEDIA_TYPE_ERROR;
ErrorRepresentation.Builder builder = create(errorStatus, ex);
String message = String.format("Not acceptable media type '%s', only %s allowed", ex.getAcceptHeader(), ex.getSupportedMediaType());
builder.message(message);
String errorBody = jsonFormatter.formatObject(builder.build());
response.addHeader("Content-Type", MediaType.TEXT_PLAIN_VALUE);
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
PrintWriter writer = response.getWriter();
writer.print(errorBody);
writer.flush();
writer.close();
}
@ExceptionHandler({RuntimeException.class, Exception.class})
public ResponseEntity<Map<String, Object>> handleGeneralError(Exception ex) {
ErrorReason errorStatus = ErrorReason.GENERAL_INTERNAL_ERROR;
ErrorRepresentation.Builder builder = create(errorStatus, ex);
log.error("general exception: "+ ExceptionUtils.getStackTrace(ex));
return createErrorResponse(builder, errorStatus);
}
//------------------------ PRIVATE --------------------------
private ErrorRepresentation.Builder create(ErrorReason errorStatus, Exception ex){
ErrorRepresentation.Builder builder = new ErrorRepresentation.Builder();
builder.httpStatus(errorStatus.httpStatusValue())
.message(ex.getMessage())
.reason(errorStatus.errorReason());
return builder;
}
private ResponseEntity<Map<String, Object>> createErrorResponse(ErrorRepresentation.Builder builder, ErrorReason errorStatus){
Map<String, Object> representation = builder.build();
return new ResponseEntity<>(representation, errorStatus.httpStatus());
}
private boolean causedByFieldError(BindException ex){
if(!ex.getAllErrors().isEmpty()){
ObjectError error = ex.getAllErrors().get(0);
return error instanceof FieldError;
} else {
return false;
}
}
private FieldError extractFieldError(BindException ex){
return (FieldError) ex.getAllErrors().get(0);
}
//------------------------ SETTERS --------------------------
@Autowired
public void setJsonFormatter(JsonFormatter jsonFormatter) {
this.jsonFormatter = jsonFormatter;
}
}